import {
  CommandDialog,
  FeatureManager,
  IAssemblyDocTreeNode,
} from '@amcax/base';
import { CommandFormWrapper } from '@app-base/common/components/command-form';
import { PickInputForFocus } from '@app-base/common/components/command-form/auto-focus/pick-input';
import { CommandFormFooter } from '@app-base/common/components/command-form/footer';
import { generateProductPickObj } from '@app-base/common/components/pick-input/pick-product';
import {
  pickedProductData,
  PickInputValueType,
} from '@app-base/common/components/pick-input/types';
import { useCommand } from '@app-base/common/hooks/useCommand';
import {
  ProductObjectDisplayType,
  useProductCommandDisplay,
} from '@app-base/common/hooks/useProductCommandDisplay';
import { useTools } from '@app-base/common/hooks/useTools';
import { DocumentContext } from '@app-base/common/providers/documentProvider';
import { ctx2, docMgr } from '@app-base/module';
import { wsApi } from '@app-base/server';
import { useFlatParts } from '@app-cad-env/product/hooks/useFlatParts';
import { Checkbox, Select, Table } from 'antd';
import { DefaultOptionType } from 'antd/es/select';
import i18n from 'i18next';
import { useContext, useEffect, useMemo, useRef, useState } from 'react';
import { Object3D } from 'three-legacy';
import { getAssembleNodesByIds } from '../../utils';
import { getInterferenceCheckColumns } from './columns';
import './style.scss';
import { CommandLabeledItemWrapper } from '@app-base/common/components/command-form/item-wrapper';

type InterferenceResult = {
  key: string;
  vdata: any;
  objects: string[];
  volume: number;
};

export type InterferenceTableData = {
  key: string;
  vdata: any;
  part1: IAssemblyDocTreeNode;
  index1: number;
  part2: IAssemblyDocTreeNode;
  index2: number;
  volume: number;
  isIgnored: boolean;
};

const EXCLUDE_OBJECTS_STORAGE_KEY = 'interference-exclude-objects';

const storedExcludeObjects: Record<string, string[]> = {};

const noInterferenceDisplayOptions: DefaultOptionType[] = [
  {
    label: '默认着色',
    value: ProductObjectDisplayType.Default,
  },
  {
    label: '隐藏状态',
    value: ProductObjectDisplayType.Hidden,
  },
  {
    label: '透明状态',
    value: ProductObjectDisplayType.Transparent,
  },
];

const Dialog = ({ title, onClose }) => {
  const currentStorageKey = `${EXCLUDE_OBJECTS_STORAGE_KEY}_${docMgr.documentID}`;

  const [includeObjects, setIncludeObjects] = useState<pickedProductData[]>([]);
  const [excludeObjects, setExcludeObjects] = useState<pickedProductData[]>([]);

  const { docTree } = useContext(DocumentContext);
  const [noInterferenceDisplay, setNoInterferenceDisplay] = useState(
    ProductObjectDisplayType.Default,
  );

  // 若之前保存了排除的对象，则重新填入
  useEffect(() => {
    try {
      const storedKeys: string[] = storedExcludeObjects[currentStorageKey];
      if (storedKeys != null && storedKeys.length > 0) {
        setExcludeObjects(
          getAssembleNodesByIds(storedKeys, docTree).map((v) =>
            generateProductPickObj(v),
          ),
        );
      }
    } catch (e) {
      console.error(e);
    }
  }, []);

  // 处理隐藏排除对象

  const [includeCoincide, setIncludeCoincide] = useState(false);
  const [calEntityForPart, setCalEntityForPart] = useState(false);
  // 子装配视为整体，内部不进行干涉
  // 对前端来说，此值为false时，需要把includeObjects中的子装配变为自己所有的子零件
  const [productAsPart, setProductAsPart] = useState(false);
  const [needSaveExcludeObjects, setNeedSaveExcludeObjects] = useState(true);
  const [isExcludeObjectsHidden, setIsExcludeObjectsHidden] = useState(false);
  const [isIgnoreHiddenObjects, setIsIgnoreHiddenObjects] = useState(true);
  const [isResultPartTransparent, setIsResultPartTransparent] = useState(false);

  const [isLoading, setIsLoading] = useState(false);
  const [hasCalculated, setHasCalculated] = useState(false);

  const [interferenceResult, setInterferenceResult] = useState<
    InterferenceResult[]
  >([]);

  const { rendererAssembly } = useTools();

  const {
    updatePartObjectsMap,
    hideObject,
    showObject,
    restoreObjectVisibility,
    restoreObjectOpacity,
    setObjectOpacity,
  } = useProductCommandDisplay();

  const includePartObjects = useMemo(() => {
    // 根节点始终视为可见
    const isNodeVisible = (node: IAssemblyDocTreeNode) =>
      node.recursivelyVisible || node.parent == null;
    const getPartOfProduct = (node: IAssemblyDocTreeNode) => {
      if (!isNodeVisible(node) && isIgnoreHiddenObjects) {
        return [];
      }
      const result: IAssemblyDocTreeNode[] = [];
      node.children?.forEach((c) => {
        if (c.type === 'part' && isNodeVisible(c)) {
          result.push(c);
        }
        if (c.type === 'product') {
          result.push(...getPartOfProduct(c));
        }
      });

      return result;
    };

    return includeObjects.flatMap((v) => {
      if (isIgnoreHiddenObjects && !isNodeVisible(v.assemblyInfo)) {
        return [];
      }

      if (productAsPart || v.assemblyInfo.type === 'part') {
        return [v.assemblyInfo];
      }

      return getPartOfProduct(v.assemblyInfo);
    });
  }, [includeObjects, productAsPart, isIgnoreHiddenObjects]);

  useEffect(() => {
    if (
      productAsPart &&
      includeObjects.some((v) => v.assemblyInfo.parent == null)
    ) {
      setIncludeObjects([]);
    }
  }, [productAsPart]);

  const excludePartObjects = useMemo(() => {
    const getPartOfProduct = (node: IAssemblyDocTreeNode) => {
      const result: IAssemblyDocTreeNode[] = [];
      node.children?.forEach((c) => {
        if (c.type === 'part' && c.recursivelyVisible) {
          result.push(c);
        }
        if (c.type === 'product') {
          result.push(...getPartOfProduct(c));
        }
      });

      return result;
    };

    return excludeObjects.flatMap((v) => {
      if (productAsPart || v.assemblyInfo.type === 'part') {
        return [v.assemblyInfo];
      }

      return getPartOfProduct(v.assemblyInfo);
    });
  }, [excludeObjects, productAsPart]);

  const onSubmit = async () => {
    setHasCalculated(true);
    if (includePartObjects.length < 2 && !calEntityForPart) {
      setInterferenceResult([]);
      return;
    }

    try {
      setIsLoading(true);

      const objects = includePartObjects.map((v) => v.key);
      const excludeKeys = excludePartObjects.map((v) => v.key);

      updatePartObjectsMap([
        ...includePartObjects,
        ...excludeObjects.map((o) => o.assemblyInfo),
      ]);

      const result: InterferenceResult[] = await wsApi.interferenceCheck({
        objects,
        exclude: excludeKeys,
        includeCoincide,
        calEntityForPart,
      });

      setInterferenceResult(
        result.map((r) => ({
          key: [r.objects[0], r.objects[1]].join('-'),
          ...r,
        })),
      );

      if (needSaveExcludeObjects) {
        storedExcludeObjects[currentStorageKey] = excludeKeys;
      }
    } finally {
      setIsLoading(false);
    }
  };

  const renders = useRef<Object3D[]>([]);
  const objectsInResult = useRef<string[]>([]);
  const [tableData, setTableData] = useState<InterferenceTableData[]>([]);
  const [selectedResult, setSelectedResult] = useState<string[]>([]);

  useEffect(() => {
    const hides = new Set<string>();
    interferenceResult.forEach((v) => v.objects.forEach((o) => hides.add(o)));
    objectsInResult.current = Array.from(hides);

    ctx2.viewer.requestRender();

    setTableData(
      interferenceResult.map((r) => {
        const [id1, index1] = r.objects[0].split(':s');
        const [id2, index2] = r.objects[1].split(':s');

        return {
          key: r.key,
          vdata: r.vdata,
          part1: includePartObjects.find(
            (o) => getAssembleNodesByIds([id1], o)?.length > 0,
          ),
          index1: index1 == null ? null : Number(index1),
          part2: includePartObjects.find(
            (o) => getAssembleNodesByIds([id2], o)?.length > 0,
          ),
          index2: index2 == null ? null : Number(index2),
          volume: r.volume,
          isIgnored: false,
        };
      }),
    );
  }, [interferenceResult]);

  // 选中与忽略挂钩，脱离点击选中
  useEffect(() => {
    setSelectedResult(tableData.filter((v) => !v.isIgnored).map((v) => v.key));
  }, [tableData]);

  const allParts = useFlatParts();

  useEffect(() => {
    renders.current.forEach((r) => {
      ctx2.cadScene.assisGroup.remove(r);
    });

    const targets = interferenceResult.filter((r) =>
      selectedResult.includes(r.key),
    );

    renders.current = rendererAssembly
      .parsePreviewModels(
        targets.map(({ vdata }) => vdata),
        {
          skipFace: !isResultPartTransparent,
          edgeColor: 0xdd0000,
          faceColor: 0x902b2b,
        },
      )
      .filter(Boolean);

    renders.current.forEach((o) => o.traverse((o) => (o.renderOrder = 99999)));

    const noInterferenceObjects = allParts
      .map((o) => o.key)
      .filter((id) => !objectsInResult.current.includes(id));

    const objectsVisibility = {
      visible: new Set<string>(),
      invisible: new Set([
        ...objectsInResult.current,
        ...(isExcludeObjectsHidden ? excludePartObjects.map((o) => o.key) : []),
        ...(noInterferenceDisplay === ProductObjectDisplayType.Hidden
          ? noInterferenceObjects
          : []),
      ]),
    };

    targets.forEach((t) => {
      objectsVisibility.visible.add(t.objects[0]);
      objectsVisibility.visible.add(t.objects[1]);

      objectsVisibility.invisible.delete(t.objects[0]);
      objectsVisibility.invisible.delete(t.objects[1]);
    });

    renders.current.forEach((r) => {
      ctx2.cadScene.assisGroup.add(r);
    });

    objectsVisibility.invisible.forEach((o) => hideObject(o));
    objectsVisibility.visible.forEach((o) => {
      showObject(o);
      if (isResultPartTransparent) {
        setObjectOpacity(o, 0.4);
      } else {
        restoreObjectOpacity(o);
      }
    });

    ctx2.viewer.requestRender();

    if (noInterferenceDisplay === ProductObjectDisplayType.Transparent) {
      noInterferenceObjects.forEach((o) => setObjectOpacity(o, 0.4));
    }

    return () => {
      objectsVisibility.invisible.forEach((o) => restoreObjectVisibility(o));
      objectsVisibility.visible.forEach((o) => restoreObjectOpacity(o));
      noInterferenceObjects.forEach((o) => restoreObjectOpacity(o));
    };
  }, [
    selectedResult,
    isResultPartTransparent,
    isExcludeObjectsHidden,
    includePartObjects,
    noInterferenceDisplay,
  ]);

  useCommand('interference-check', {
    finishDraw: onSubmit,
    onClose,
  });

  const setTableRowIgnored = (key: string, ignored: boolean) => {
    setTableData((old) => {
      const target = old.find((o) => o.key === key);
      target.isIgnored = ignored;
      return [...old];
    });
  };

  return (
    <CommandDialog
      title={title}
      onClose={onClose}
      placement='topLeft'
      className='cmd-interference-check'
    >
      <CommandFormWrapper>
        <CommandLabeledItemWrapper label='所选的零部件'>
          <PickInputForFocus
            allowPickType={
              productAsPart
                ? [PickInputValueType.Product]
                : [PickInputValueType.Product, PickInputValueType.FullProduct]
            }
            onPick={(v) => {
              setIncludeObjects(v);
            }}
            autoFocus
            placeholder='请选择零部件'
            value={includeObjects}
            isMulti
            highlightSelection={interferenceResult.length === 0}
            allowPickInvisibleAssembleFromLeftPanel
            noDuplicateProduct
          />
        </CommandLabeledItemWrapper>
        <CommandLabeledItemWrapper label='排除的零部件'>
          <PickInputForFocus
            allowPickType={[PickInputValueType.Product]}
            onPick={(v) => {
              setExcludeObjects(v);
            }}
            placeholder='请选择排除的零部件'
            value={excludeObjects}
            isMulti
            highlightSelection={
              interferenceResult.length === 0 && !isExcludeObjectsHidden
            }
            allowPickInvisibleAssembleFromLeftPanel
          />
        </CommandLabeledItemWrapper>
        <Checkbox
          checked={includeCoincide}
          onChange={(e) => {
            setIncludeCoincide(e.target.checked);
          }}
        >
          视重合为干涉
        </Checkbox>
        {FeatureManager.isFeatureEnabled('interference-check-multi-entity') && (
          <Checkbox
            checked={calEntityForPart}
            onChange={(e) => {
              setCalEntityForPart(e.target.checked);
            }}
          >
            多实体干涉
          </Checkbox>
        )}
        <Checkbox
          checked={productAsPart}
          onChange={(e) => {
            setProductAsPart(e.target.checked);
          }}
        >
          视子装配为零部件
        </Checkbox>
        <Checkbox
          checked={needSaveExcludeObjects}
          onChange={(e) => {
            const checked = e.target.checked;
            setNeedSaveExcludeObjects(checked);

            if (!checked) {
              delete storedExcludeObjects[currentStorageKey];
            }
          }}
        >
          记住排除的零部件
        </Checkbox>
        <Checkbox
          checked={isExcludeObjectsHidden}
          onChange={(e) => {
            setIsExcludeObjectsHidden(e.target.checked);
          }}
        >
          隐藏排除的零部件
        </Checkbox>
        <Checkbox
          checked={isIgnoreHiddenObjects}
          onChange={(e) => {
            setIsIgnoreHiddenObjects(e.target.checked);
          }}
        >
          忽略隐藏的零部件
        </Checkbox>
        <Checkbox
          checked={isResultPartTransparent}
          onChange={(e) => {
            setIsResultPartTransparent(e.target.checked);
          }}
        >
          透明显示干涉零件
        </Checkbox>
        <div className='text-sm mt-2'>非干涉部件显示状态</div>
        <Select
          placeholder='非干涉部件显示状态'
          options={noInterferenceDisplayOptions}
          value={noInterferenceDisplay}
          onChange={(val) => {
            setNoInterferenceDisplay(val);
          }}
        />
        {hasCalculated ? (
          <Table
            className='max-w-[600px]'
            pagination={false}
            rowSelection={{
              selectedRowKeys: selectedResult,
              renderCell: () => null,
              hideSelectAll: true,
            }}
            scroll={{ y: 300 }}
            dataSource={tableData}
            locale={{
              emptyText: (
                <div className='h-24 flex items-center justify-center'>
                  未干涉
                </div>
              ),
            }}
            columns={getInterferenceCheckColumns(setTableRowIgnored)}
          />
        ) : (
          <div className='min-w-[500px]'></div>
        )}
        <CommandFormFooter
          onSubmit={onSubmit}
          onClose={onClose}
          submitText='计算'
          isSubmitLoading={isLoading}
        />
      </CommandFormWrapper>
    </CommandDialog>
  );
};

export const cmdInterferenceCheck = {
  id: 'CmdInterferenceCheck',
  label: 'command.interference-check',
  icon: 'commands/product/interference-check',
  create: (label?: string, onClose?: () => void) => (
    <Dialog
      title={i18n.t(label)}
      onClose={onClose}
    />
  ),
};
